package com.hontech.kaltaapp.device

import com.hontech.kaltaapp.device.codec.CodecType
import com.hontech.kaltaapp.device.codec.Uint8
import com.hontech.kaltaapp.device.codec.u8
import com.hontech.kaltaapp.device.event.DeviceStatus
import com.hontech.kaltaapp.device.event.onLog
import com.hontech.kaltaapp.device.event.onRecvStatus
import com.hontech.kaltaapp.exception.AckTimeoutException
import com.hontech.kaltaapp.exception.PackageInvalidException
import com.hontech.kaltaapp.serial.SerialPort
import com.hontech.kaltaapp.serial.TTYSerialPort
import com.hontech.kaltaapp.serial.usb.UsbSerialPortMod
import com.hontech.kaltaapp.utils.AppTask
import com.hontech.kaltaapp.utils.await
import kotlinx.coroutines.CompletableDeferred
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap

object Conn {


    private var recvTask: RecvTask? = null

    fun isOpen() = recvTask != null

    suspend fun open(name: String) {
        close()

        val port = if (name == SerialPort.USB_PORT_NAME) {
            UsbSerialPortMod.open()
        } else {
            TTYSerialPort(name)
        }
        recvTask = RecvTask(port)
    }

    suspend fun close() {
        if (recvTask == null) {
            return
        }
        val task = recvTask
        recvTask = null

        task!!.close()
    }

    private fun assertOpen() {
        if (recvTask == null) {
            throw IOException("串口没有打开")
        }
    }

    suspend fun simpleReq(cmd: Int, args: Array<CodecType>, result: Array<CodecType>) {
        assertOpen()
        val seq = Proto.createSeq()
        val res = recvTask!!.createPromise(Type.SIMPLE_RES, seq)
        recvTask!!.port.writeForAwait( Proto.make(Type.SIMPLE_REQ, seq, cmd.u8(), *args) )
        val frame = res.await(500)
        if (cmd != frame.cmd()) {
            throw IllegalStateException("返回CMD错误")
        }
        frame.parseRes(*result)
    }

    suspend fun ping() {
        assertOpen()
        val seq = Proto.createSeq()
        val pong = recvTask!!.createPromise(Type.PONG, seq)
        recvTask!!.port.writeForAwait(Proto.make(Type.PING, seq))
        pong.await(500)
    }

    private suspend fun checkTask(seq2: Int, cmd2: Int): Boolean {
        val seq = Uint8()
        val cmd = Uint8()
        simpleReq(Cmd.GET_TASK, arrayOf(), arrayOf(seq, cmd))
        return (seq.value == seq2) && (cmd.value == cmd2)
    }

    private suspend fun session() {
        val seq = Proto.createSeq()
        val buf = Proto.make(Type.SESSION, seq)
        val promise = recvTask!!.createPromise(Type.ACK, seq)
        recvTask!!.port.writeForAwait(buf)
        promise.await(500)
    }

    suspend fun req(cmd: Int, args: Array<CodecType>, result: Array<CodecType>) {
        assertOpen()
        session()
        val seq = Proto.createSeq()
        val buf = Proto.make(Type.REQ, seq, cmd.u8(), *args)
        val ack = recvTask!!.createPromise(Type.ACK, seq)
        val res = recvTask!!.createPromise(Type.RES, seq)
        recvTask!!.port.writeForAwait(buf)
        ack.await(500)
        while (true) {
            try {
                val frame = res.await(3000)
                recvTask!!.port.writeForAsync( Proto.make(Type.ACK, seq) )
                frame.parseRes(*result)
                return
            } catch (e: AckTimeoutException) {
                e.printStackTrace()
                if (!checkTask(seq, cmd)) {
                    val frame = res.await(3000)
                    recvTask!!.port.writeForAsync( Proto.make(Type.ACK, seq) )
                    frame.parseRes(*result)
                    return
                }
            }
        }
    }

}

private class RecvTask(val port: SerialPort) : Thread("recv-task") {

    private val frameRouter = HashMap<Int, CompletableDeferred<RecvFrame>>()
    private val closePromise = CompletableDeferred<Unit>()

    init {
        start()
    }
    suspend fun close() {
        port.notifyClose()
        closePromise.await()
        port.close()
    }

    fun createPromise(type: Int, seq: Int): CompletableDeferred<RecvFrame> {
        val promise = CompletableDeferred<RecvFrame>()
        val key = (type shl 8) + seq
        frameRouter[key] = promise
        return promise
    }

    private fun onDispatch(frame: RecvFrame) {
        val key = (frame.type() shl 8) + frame.seq()
        AppTask.post {
            val promise = frameRouter[key]
            if (promise != null) {
                promise.complete(frame)
                frameRouter.remove(key)
            }
        }
    }

    override fun run() {

        while (true) {

            try {
                val frame = port.readOnce()
                dispatch(frame)
            } catch (e: PackageInvalidException) {
                e.printStackTrace()
            } catch (e: Exception) {
                e.printStackTrace()
                break
            }
        }
        closePromise.complete(Unit)
    }

    private fun dispatch(frame: RecvFrame) {
        when (frame.type()) {
            Type.ACK, Type.PONG, Type.RES, Type.SIMPLE_RES -> onDispatch(frame)
            Type.NOTIFY -> onNotify(frame)
            Type.NOTIFY_ACK -> onNotifyAck(frame)
        }
    }

    private fun onNotifyAck(frame: RecvFrame) {
        val seq = frame.seq()
        port.writeForAsync(Proto.make(Type.ACK, seq))
    }

    private fun onNotify(frame: RecvFrame) {
        when (frame.cmd()) {
            Cmd.STATUS -> onRecvStatus(frame)
            Cmd.LOG -> onLog(frame)
        }
    }

}



